package org.ms.mconf.sdk;

import lombok.Getter;
import org.micro.collection.ConcurrentHashSet;
import org.micro.URL;
import org.micro.extension.ExtensionLoader;
import org.ms.mconf.sdk.model.MetaConfig;
import org.ms.mconf.sdk.model.MetaDataConfig;
import org.ms.mconf.sdk.model.MetaMultiDataConfig;
import org.ms.mconf.sdk.service.IOpsService;

import java.util.List;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * The Micro Service Config Center
 *
 * @author lry
 */
@Getter
public enum MicroConfig {

    /**
     * The SDK Client Support
     */
    SDK {
        @Override
        public void startup(NotifyListener listener) {
            // subscribe to all configuration of the current application
            configService.subscribe(list -> compareClearAndAdd(list, metaMultiDataConfigs, listener), application);

            // initialize cycle pull executor thread pool
            this.buildScheduledExecutor("sdk");

            // periodically pull all the configuration of the current application
            cycleScheduledExecutorService.scheduleAtFixedRate(() -> {
                List<MetaMultiDataConfig> list = configService.pull(application);
                compareClearAndAdd(list, metaMultiDataConfigs, listener);
            }, initialDelay, period, TimeUnit.MILLISECONDS);

            // add ShutdownHook
            Runtime.getRuntime().addShutdownHook(new Thread(this::destroy));
        }

        @Override
        public void destroy() {
            if (configService != null) {
                configService.unsubscribe(application);
            }
            super.destroy();
        }
    },

    /**
     * The Ops Server Support
     */
    OPS {
        @Override
        public void startup(NotifyListener listener) {
            // subscribe to all configuration of the all application
            configService.subscribe(list -> compareClearAndAdd(list, metaDataConfigs, listener));

            // initialize cycle pull executor thread pool
            this.buildScheduledExecutor("ops");

            // periodically pull all the configuration of the current application
            cycleScheduledExecutorService.scheduleAtFixedRate(() -> {
                List<MetaDataConfig> list = configService.search(new MetaConfig(), null);
                compareClearAndAdd(list, metaDataConfigs, listener);
            }, initialDelay, period, TimeUnit.MILLISECONDS);

            // add ShutdownHook
            Runtime.getRuntime().addShutdownHook(new Thread(this::destroy));
        }

        @Override
        public void destroy() {
            if (configService != null) {
                configService.unsubscribe();
            }
            super.destroy();
        }
    };

    protected String application;
    protected IOpsService configService;

    private final Object LOCK = new Object();

    protected long period;
    protected long initialDelay;
    protected URL url;
    protected ScheduledExecutorService cycleScheduledExecutorService;
    protected Set<MetaMultiDataConfig> metaMultiDataConfigs = new ConcurrentHashSet<>();
    protected Set<MetaDataConfig> metaDataConfigs = new ConcurrentHashSet<>();

    /**
     * The initialize config center
     *
     * @param url {@link URL}
     */
    public void initialize(URL url) {
        this.url = url;
        this.application = url.getParameter(Constants.APPLICATION_KEY);
        this.period = Constants.getLongValue(url, Constants.PULL_EXECUTOR_PERIOD);
        this.initialDelay = Constants.getLongValue(url, Constants.PULL_EXECUTOR_INITIAL_DELAY);

        this.configService = ExtensionLoader.getLoader(IOpsService.class).getExtension(url.getProtocol());
        configService.initialize(url);
    }

    /**
     * The destroy config center
     */
    public void destroy() {
        if (configService != null) {
            configService.destroy();
        }
    }

    /**
     * The build cycle pull executor thread pool
     *
     * @param prefix id prefix name
     */
    protected void buildScheduledExecutor(String prefix) {
        final AtomicInteger pullCounter = new AtomicInteger(0);
        int corePoolSize = Constants.getIntValue(url, Constants.PULL_EXECUTOR_CORE_POOL_SIZE);
        this.cycleScheduledExecutorService = new ScheduledThreadPoolExecutor(corePoolSize, r -> {
            Thread t = new Thread(r);
            t.setName(String.format("%s-config-cycle-pull-executor-%s", prefix, pullCounter.getAndIncrement()));
            t.setDaemon(true);
            return t;
        });
    }

    /**
     * The Compare Clear And Add
     *
     * @param list     {@link List}
     * @param set      {@link Set}
     * @param listener {@link NotifyListener}
     * @param <C>      config model
     */
    @SuppressWarnings("unchecked")
    protected final <C> void compareClearAndAdd(List<C> list, Set<C> set, NotifyListener listener) {
        if (list == null || list.isEmpty()) {
            return;
        }
        if (list.containsAll(set) && set.containsAll(list)) {
            return;
        }

        synchronized (LOCK) {
            set.clear();
            set.addAll(list);
        }

        if (listener == null) {
            return;
        }
        listener.notify(list);
    }

    /**
     * The startup config center
     */
    public void startup() {
        this.startup(null);
    }

    /**
     * The startup config center
     *
     * @param listener {@link NotifyListener}
     * @param <L>      {@link NotifyListener}
     */
    public abstract <L> void startup(NotifyListener<L> listener);

}
